import { MeshBuilder } from '@babylonjs/core/Meshes/meshBuilder';
import { Mesh } from '@babylonjs/core/Meshes/mesh';
import { Quaternion, Vector2, Vector3 } from '@babylonjs/core/Maths/math.vector';
import { DefaultStage } from '../default-stage';
import { PointerEventTypes, PointerInfo } from '@babylonjs/core/Events/pointerEvents';
import { ArcRotateCamera } from '@babylonjs/core/Cameras/arcRotateCamera';
import { AnimationFrame, Color3, TransformNode } from 'babylon-lib/index';
import { StandardMaterial } from '@babylonjs/core/Materials/standardMaterial';
import { RawAssetsUrl } from '../../raw-asset-info';
import { Calculation } from 'babylon-lib/tool/calculation';
import { Constants } from '@babylonjs/core/Engines/constants';
import { CLICK_THRESHOLD } from '../../data/data';


class NavigatePlane {

    public static _active: boolean = true;

    private static _mesh: Mesh;
    private static _mark: Mesh;
    private static _down = false;
    private static _tv0 = new Vector3();
    private static _tv1 = new Vector3();
    private static _stage: DefaultStage;
    private static _lerpReq = {};
    private static _beginPointer = new Vector2();
    private static _endPointer = new Vector2();
    private static _markReq = {};
    private static _markMap = [0, 0.5, 0.5, 1.2, 1, 0.3];
    private static _markContainer: TransformNode;

    public static initialize(stage: DefaultStage): void {
        this._stage = stage;
        this._markContainer = new TransformNode("navigatePlaneMarkContainer");
        this._mesh = this.initializePlane(stage);
        this._mark = this.initializeMark(stage);
        this.bindEvent(stage);
    }

    public static set active(active: boolean) {
        this._active = active;
        AnimationFrame.cancel(this._lerpReq);
    }

    public static get plane(): Mesh {
        return this._mesh;
    }

    private static bindEvent(stage: DefaultStage): void {
        stage.mainScene.onPointerObservable.add(NavigatePlane.pointerHandle);
    }

    private static pointerHandle(e: PointerInfo): void {
        const scope = NavigatePlane;
        if (!scope._active) {
            scope._down = false;
            return;
        }
        if (!e.pickInfo) return;
        switch (e.type) {
            case PointerEventTypes.POINTERDOWN:
                if (!e.pickInfo.pickedMesh) return;
                if (e.pickInfo.pickedMesh.uniqueId !== scope._mesh.uniqueId) return;
                scope._down = true;
                scope._tv0.copyFrom(e.pickInfo.pickedPoint!);
                scope._beginPointer.set(e.event.clientX, e.event.clientY);
                break;
            case PointerEventTypes.POINTERMOVE:
                if (!scope._down) return;
                scope._endPointer.set(e.event.clientX, e.event.clientY);
                if (Vector2.DistanceSquared(scope._beginPointer, scope._endPointer) > CLICK_THRESHOLD) {
                    scope._down = false;
                }
                break;
            case PointerEventTypes.POINTERUP:
                if (!scope._down) return;
                scope._down = false;
                scope.lerpCamera(scope._tv0);
                scope.playMark(scope._tv0);
                break;
        }
    }

    private static playMark(position: Vector3): void {
        const scope = NavigatePlane;
        AnimationFrame.cancel(scope._markReq);

        scope._markContainer.position.copyFrom(position);
        const dis = Vector3.Distance(scope._stage.mainCamera.globalPosition, position);
        const r = dis * 0.08;
        scope._markContainer.scaling.set(r, r, r);

        scope._mark.isVisible = true;
        let t = 0;
        AnimationFrame.request(() => {
            t += 0.05;
            const s = Calculation.valueLinearMapping(t, 0, 1, scope._markMap);
            scope._mark.scaling.set(s, s, s);
            if (t >= 1) {
                scope._mark.isVisible = false;
                return true;
            }
        }, 16, 200, scope._markReq);
    }

    private static lerpCamera(position: Vector3): void {
        const scope = NavigatePlane;
        scope._tv1.copyFrom(position);
        AnimationFrame.cancel(scope._lerpReq);
        const camera = scope._stage.mainCamera as ArcRotateCamera;
        let t = 0;
        AnimationFrame.request(() => {
            t += 0.01;
            Vector3.LerpToRef(camera.target, scope._tv1, t, camera.target);
            return t >= 1;
        }, 16, 200, scope._lerpReq);
    }

    private static initializePlane(stage: DefaultStage): Mesh {
        const plane = MeshBuilder.CreatePlane('navigatePlane', { width: 150, height: 150 }, stage.mainScene);
        plane.rotationQuaternion = Quaternion.Identity();
        Quaternion.RotationYawPitchRollToRef(0, Math.PI * 0.5, 0, plane.rotationQuaternion);
        plane.visibility = 0;
        return plane;
    }

    private static initializeMark(stage: DefaultStage): Mesh {
        const plane = MeshBuilder.CreatePlane('navigatePlaneMark', { width: 1, height: 1 }, stage.mainScene);
        plane.parent = this._markContainer;
        const s = this._markMap[1];
        plane.position.set(0, 0, 0);
        plane.scaling.set(s, s, s);
        plane.rotationQuaternion = Quaternion.FromEulerAngles(1.5708, 0, 0);

        const tex = stage.loader(stage.mainScene).getTextureTaskInfo(RawAssetsUrl.textureCircle)?.texture!;
        const mat = new StandardMaterial('navigatePlaneMarkMaterial', stage.mainScene);
        mat.disableLighting = true;
        mat.transparencyMode = 1;
        mat.opacityTexture = tex;
        mat.emissiveColor = Color3.FromHexString('#00aaff');
        mat.depthFunction = Constants.ALWAYS;

        plane.material = mat;
        plane.isPickable = false;
        plane.isVisible = false;
        return plane;
    }

}


export { NavigatePlane };